function [varargout] = transio(varargin)
% transio (Object Class)
%
% FORMAT:       tio_obj = transio(file, endian, class, offset, size);
%
% Input fields:
%
%       file        filename where data is stored in
%       endian      endian type (e.g. 'le', or 'ieee-be')
%       class       numerical class (e.g. 'uint16', 'single')
%       offset      offset within file (in bytes)
%       size        size of array in file
%
% Output fields:
%
%       tio_obj     transio object that supports the methods
%                   - subsref  : tio_obj(I) or tio_obj(IX, IY, IZ)
%                   - subsasgn : same as subsref
%                   - size     : retrieving the array size
%                   - end      : for building relative indices
%                   - display  : showing information
%
% Note 1: enlarging of existing files (if the array is the last element
%         in the file) can be done by adding a (class-independent) sixth
%         parameter to the call.
%
% Note 2: both subsref and subsasgn will only work within the existing
%         limits; growing of the array as with normal MATLAB variables
%         is *NOT* supported--so tio_obj(:,:,ZI) = []; will *NOT* work!

% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Version:      v0.7g
% Build:        9033019
% Date:         Mar-30 2009, 7:45 PM CEST
% Author:       Jochen Weber, SCAN Unit, Columbia University, NYC, NY, USA
%
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% This Class provides transparent IO access to files that store large
% variables at certain place.
%
% When accessed, the file is opened and closed each time! Since MATLAB
% does not offer destructors, it is virtually impossible to keep track
% of open files!
%
% The construction of a transio object can be done with a syntax of:
%
% tio = transio(file, endian, class, offset, size [, enlarge]);
%
% When the fifth argument is not given, transio will issue an error
% if the filesize is not sufficient.

% declare persistent stored variable
persistent transio_singleton;

% check class initialization
if isempty(transio_singleton) || ...
   ~isstruct(transio_singleton) || ...
   ~transio_singleton.is_initialized

    % initialize persistent struct
    transio_singleton = struct;
    transio_singleton.is_initialized = false;

    % perform init commands
    transio_singleton.validclasses = struct( ...
        'double',  8, ...
        'int16',   2, ...
        'int32',   4, ...
        'int64',   8, ...
        'int8',    1, ...
        'single',  4, ...
        'uint16',  2, ...
        'uint32',  4, ...
        'uint64',  8, ...
        'uint8',   1  ...
    );
    transio_singleton.validcnames = fieldnames(transio_singleton.validclasses);
    transio_singleton.validendian = struct( ...
        'be',      false, ...
        'le',      true,  ...
        'ieee_be', false, ...
        'ieee_le', true,  ...
        'ieeebe',  false, ...
        'ieeele',  true   ...
    );
    transio_singleton.validenames = fieldnames(transio_singleton.validendian);

    % say we are initialized
    transio_singleton.is_initialized = true;
end

% check arguments > object creation call
if nargin > 4 && ...
    ischar(varargin{1}) && ...
    ~isempty(varargin{1}) && ...
    any(strcmp(makelabel(lower(varargin{2}(:)')), transio_singleton.validenames)) && ...
    any(strcmpi(varargin{3}(:)', transio_singleton.validcnames)) && ...
    isa(varargin{4}, 'double') && ...
    numel(varargin{4}) == 1 && ...
   ~isinf(varargin{4}) && ...
   ~isnan(varargin{4}) && ...
    fix(varargin{4}) == varargin{4} && ...
    varargin{4} >= 0 && ...
    isa(varargin{5}, 'double') && ...
   ~isempty(varargin{5}) && ...
    numel(varargin{5}) == length(varargin{5}) && ...
    length(varargin{5}) < 32 && ...
   ~any(isinf(varargin{5}) | isnan(varargin{5}) | fix(varargin{5}) ~= varargin{5}) && ...
   ~any(varargin{5} < 1 | varargin{5} > 2147483648)

    % make absolute filename
    [isabs, filename] = isabsolute(varargin{1}(:)');

    % open the file
    try
        rdo = false;
        if nargin < 6
            fid = fopen(filename, 'r+');
        else
            fid = fopen(filename, 'a+');
        end
        if fid < 1
            rdo = true;
            fid = fopen(filename, 'r');
            if fid < 1
                error('INVALID_FID');
            end
        end
    catch
        error( ...
            'transio:ErrorOpeningFile', ...
            'Error opening file ''%s''.', ...
            filename ...
        );
    end

    % calculate most significant position
    lnd = transio_singleton.validendian.(makelabel(lower(varargin{2}(:)')));
    cls = lower(varargin{3}(:)');
    csz = transio_singleton.validclasses.(cls);
    obs = varargin{5}(:)';
    while ~isempty(obs) && ...
        obs(end) == 1
        obs(end) = [];
    end
    while numel(obs) < 2
        obs(2) = 1;
    end
    obs = obs(:)';
    ofs = varargin{4};
    msp = ofs + csz * prod(obs);

    % go to end position and check size
    fseek(fid, 0, 1);
    fsz = ftell(fid);

    % file too short and no enlarging
    if fsz < msp && ...
       (nargin < 6 || ...
        rdo)

        % give an error
        error( ...
            'transio:FileTooShort', ...
            'The file is too short to read/write the data.' ...
        );

    % enlarging
    elseif fsz < msp

        % create array to write
        war = uint8(0);
        war(1:1048576) = war(1);
        tfsz = fsz;
        mspf = msp - 1048576;
        try
            while tfsz < mspf
                fwrite(fid, war, 'uint8');
                tfsz = tfsz + 1048576;
            end
            fwrite(fid, war(1:(msp - tfsz)), 'uint8');
            % check again
            fseek(fid, 0, 1);
            if ftell(fid) ~= msp
                error('ENLARGE_ERROR');
            end
        catch
            error( ...
                'transio:ErrorEnlargingFile', ...
                'Error while enlarging the file.' ...
            );
        end
    end

    % close file again, we don't keep it open anyway
    fclose(fid);

    % build object
    sobj.FileName = filename;
    sobj.LittleND = lnd;
    sobj.DataType = cls;
    sobj.TypeSize = csz;
    sobj.IOOffset = ofs;
    sobj.DataDims = obs;
    sobj.ReadOnly = rdo;
    obj = builtin('class', sobj, 'transio');

    % return object
    varargout{1} = obj;

% object creation
elseif nargin > 1 && ...
    isa(varargin{1}, 'double') && ...
    ischar(varargin{2}) && ...
    ~isempty(varargin{2}) && ...
    strcmp(makelabel(varargin{2}(:)'), varargin{2}(:)')

    % what action to perform
    switch lower(varargin{2}(:)')
        case {'makeobject'}
            if isstruct(varargin{3}) && ...
                numel(varargin{3}) == 1 && ...
                length(fieldnames(varargin{3})) == 7 && ...
                all(strcmp(fieldnames(varargin{3}), { ...
                    'FileName'; ...
                    'LittleND'; ...
                    'DataType'; ...
                    'TypeSize'; ...
                    'IOOffset'; ...
                    'DataDims'; ...
                    'ReadOnly'}))
                varargout{1} = builtin('class', varargin{3}, 'transio');
            end
        otherwise
            if ~any(strcmpi(transio_singleton.validcnames, varargin{2}(:)'))
                error( ...
                    'transio:BadArgument', ...
                    'Unknown action string: %s.', ...
                    varargin{2}(:)' ...
                );
            end
            varargout{1} = ...
                transio_singleton.validclasses.(lower(varargin{2}(:)'));
    end

% default constructor
elseif nargin == 0
    varargout{1} = builtin('class', struct( ...
        'FileName', 'NONE', ...
        'LittleND', 1, ...
        'DataType', 'double', ...
        'TypeSize', 8, ...
        'IOOffset', 0, ...
        'DataDims', [0, 0], ...
        'ReadOnly', true), 'transio');
    
% otherwise give an error
else
    error( ...
        'transio:BadArgument', ...
        'Invalid combination of arguments.' ...
    );
end
